package parser;

import java.io.*;
import lexer.*;
import symbols.*;
import inter.*;

public class Parser {
    private Lexer lex;
    private Token look;
    private Env top = null;
    int used = 0;

    public Parser(Lexer l) throws Exception {
	lex = l;
	move();
    }
    private void error(String s) throws Exception {
	throw new Error("near line " + lex.lineno + ": " + s);
    }
    private void move() throws Exception {
	look = lex.scan();
    }
    private void match(int t) throws Exception {
	if (look.tag == t)
	    move();
	else
	    error("syntax error");
    }
    public void program() throws Exception {
	proghdr();
	Stmt s = block();
	int begin = s.newlabel();
	int after = s.newlabel();
	s.emitlabel(begin);
	s.gen(begin, after);
	s.emitlabel(after);
    }
    private void proghdr() throws Exception{
	match(Tag.PROGRAM);
	match(Tag.ID);
	match(';');
    }
    private Stmt block() throws Exception{
	Env savedEnv = top;
	top = new Env(top);
	typedecls();
	vardecls();
	Stmt s = body();
	top = savedEnv;
	return s;
    }
    private void typedecls() throws Exception{
	while (look.tag == Tag.TYPE) {
	    match(Tag.TYPE);
	    Token tok = look;
	    match(Tag.ID);
	    if (top.get(tok) != null)
		error("multiple declared: " + tok.toString());
	    match('=');
	    Type p = type();
	    match(';');
	    Id id = new Id((Word)tok, p, -1);
	    top.put(tok, id);
	}
    }
    private Type type() throws Exception {
	Token w = look;
	switch(look.tag) {
	case Tag.BASIC:
	    match(Tag.BASIC);
	    return (Type)w;
	case Tag.ARRAY:
	    match(Tag.ARRAY);
	    match('[');
	    Token tok = look;
	    match(Tag.NUM);
	    match(']');
	    match(Tag.OF);
	    Token tok2 = look;
	    match(Tag.BASIC);
	    return new Array(((Num)tok).value, (Type)tok2);
	case Tag.RECORD:
	    move();
	    Env.push(top);
	    top = new Env();
	    int off = 0;
	    while (look.tag != Tag.END) {
		Type p2 = type();
		Token tok3 = look;
		match(Tag.ID);
		match(';');
		Id id = new Id((Word)tok3, p2, off);
		off += p2.width;
		top.put(tok3, id);
	    }
	    Record r = new Record(top);
	    r.width = off;
	    move();
	    top = Env.pop();
	    return r;
	case Tag.ID:
	    move();
	    Id t = top.get(w);
	    if (t == null)
		error("unknown type: "+look.toString());
	    // -1 type, -2 proc
	    if (t.offset != -1)
		error("not a type: "+look.toString());
	    
	    return t.type;
	default:
	    error("unknown type: "+look.toString());
	}
	return null;
    }
    private void vardecls() throws Exception{
	boolean multi;
	while (look.tag == Tag.VAR) {
	    move();
	    multi = true;
	    Type p = type();
	    while (multi) {
		Token tok = look;
		match(Tag.ID);
		Id id = new Id((Word)tok, p, used);
		top.put(tok, id);
		used += p.width;
		if (look.tag == ';') {
		    multi = false;
		    move();
		}
		else if (look.tag == ',') {
		    move();
		}
		else {		// invalid token
		    error("syntax error");
		}
	    }
	}
    }
    private Stmt body() throws Exception{
	Stmt s1 = Stmt.Null;
	if (look.tag == Tag.PROCEDURE) {
	    match(Tag.PROCEDURE);
	    Token tok = look;
	    match(Tag.ID);
	    Filo.pushp(tok.toString());
	    int argc = 0;
	    ProcDecl pd = new ProcDecl(tok.toString(), Tag.PROCEDURE, 0);
	    match('(');
	    Filo.push(used);
	    used = 0;
	    while (look.tag != ')') {
		Type p = type();
		Token t = look;
		Id id = new Id((Word)t, p, used);
		used += p.width;
		pd.push(p);
		if (look.tag == ')')
		    break;
		match(';');
	    }
	    match(')');
	    match(';');
	    top.put(tok, new Id((Word)tok, pd, -1));
	    s1 = block();
	}
	match(Tag.BEGIN);
	Stmt s2 = proc();
	match(Tag.END);
	used = Filo.pop();
	return new Seq(s1, s2);
    }
    private Stmt proc() throws Exception{
	Stmt s = stmts();
	if (! Filo.pempty())
	    return new Proc(Filo.popp() , s);
	return s;
    }
    private Stmt stmts() throws Exception{
	if (look.tag == Tag.END)
	    return Stmt.Null;
	else
	    return new Seq(stmt(), stmts());
    }
    private Stmt stmt() throws Exception {
	Expr x;
	Stmt seq1, seq2;
	seq1 = seq2 = Stmt.Null;
	Stmt savedStmt;		// for break
	switch(look.tag) {
	case ';':
	    move();
	    return Stmt.Null;
	case Tag.IF:
	    move();
	    match('(');
	    x = bool();
	    match(')'); match(';');
	    match(Tag.THEN);
	    while (look.tag != Tag.ELSE) {
		seq1 = new Seq(seq1, stmt());
	    }
	    move();
	    while (look.tag != Tag.FI) {
		seq2 = new Seq(seq2, stmt());
	    }
	    move();
	    return new Else(x, seq1, seq2);
	case Tag.WHILE:
	    While whilenode = new While();
	    savedStmt = Stmt.Enclosing;
	    Stmt.Enclosing = whilenode;
	    move(); match('(');
	    x = bool();
	    match(')'); match(';');
	    match(Tag.DO);
	    while (look.tag != Tag.DONE) {
		seq1 = new Seq(seq1, stmt());
	    }
	    move();
	    whilenode.init(x, seq1);
	    Stmt.Enclosing = savedStmt;
	    return whilenode;
	case Tag.BREAK:
	    move();
	    match(';');
	    return new Break();
	case Tag.READ:
	    return read();
	case Tag.WRITE:
	    return write();
	case Tag.RETURN:
	    return new Return();
	default:
	    return assign();
	}
    }
    private Stmt assign() throws Exception {
	Stmt stmt = null;
	Token t = look;
	match(Tag.ID);
	Id id = top.get(t);
	if (id == null)
	    error(t.toString()+" undeclared");
	if (look.tag == '=') {
	    move();
	    stmt = new Set(id, bool());
	}
	else if (look.tag == '[') {
	    Access x = offset(id);
	    match('=');
	    stmt = new SetElem(x, bool());
	}
	else if (look.tag == '.') {
	    Field x = field(id);
	    match('=');
	    stmt = new SetField(x, bool());
	}
	else
	    error("illegal statement");
	match(';');
	return stmt;
    }
    private Stmt read() throws Exception{
	match(Tag.READ);
	match('(');
	Stmt s = Stmt.Null;
	Token tok = look;
	Id id = top.get(tok);
	match(Tag.ID);
	switch (look.tag) {
	case '[':		// elem of array
	    s = new Read(offset(id));
	    match(')');
	    return s;
	case '.':		// field of record
	    s = new Read(field(id));
	    match(')');
	    return s;
	case ')':
	    match(')');
	    match(';');
	    if (id == null)
		error(tok.toString() + " undeclared");
	    if (id.offset < 0)
		error(tok.toString() + " not a var");
	    return new Read(id);
	default:
	    error("syntax error");
	}
	// will not reach here
	return Stmt.Null;
    }
    private Stmt write() throws Exception{
	move();
	match('(');
	Expr x = expr();
	match(')');
	match(';');
	return new Write(x);
    }
    private Expr bool() throws Exception {
	return equality();
    }
    private Expr equality() throws Exception {
	Expr x = rel();
	while (look.tag == '=') {
	    Token tok = look;
	    move();
	    x = new Rel(tok, x, rel());
	}
	return x;
    }
    private Expr rel() throws Exception {
	Expr x = expr();
	switch (look.tag) {
	case '<':
	    Token tok = look;
	    move();
	    return new Rel(tok, x, expr());
	default:
	    return x;
	}
    }
    private Expr expr() throws Exception {
	Expr x = term();
	while (look.tag == '+' || look.tag == '-') {
	    Token tok = look;
	    move();
	    x = new Arith(tok, x, term());
	}
	return x;
    }
    private Expr term() throws Exception {
	Expr x = unary();
	while (look.tag == '*' || look.tag == '/') {
	    Token tok = look;
	    move();
	    x = new Arith(tok, x, unary());
	}
	return x;
    }
    private Expr unary() throws Exception {
	if (look.tag == '-') {
	    move();
	    return new Unary(Word.minus, unary());
	}
	else
	    return factor();
    }
    private Expr factor() throws Exception {
	Expr x = null;
	switch (look.tag) {
	case '(':
	    move(); x = bool(); match(')');
	    return x;
	case Tag.NUM:
	    x = new Constant(look, Type.Int);
	    move();
	    return x;
	case '\'':		// 
	    move();
	    x = new Constant(look, Type.Char);
	    move();
	    match('\'');
	    return x;
	case Tag.ID:
	    String s =look.toString();
	    Id id = top.get(look);
	    if (id == null)
		error(look.toString() + " undeclared");
	    move();
	    if (look.tag != '[' && look.tag != '.')
		return id;
	    else if (look.tag == '[')
		return offset(id);
	    else if (look.tag == '.')
		return field(id);
	    return null;
	}
	return null;
    }
    private Access offset(Id a) throws Exception {
	Expr i; Expr w; Expr loc;
	Type type = a.type;
	match('['); i = bool(); match(']');
	type = ((Array)type).of;
	w = new Constant(type.width);
	loc = new Arith(new Token('*'), i, w);
	return new Access(a, loc, type);
    }
    private Field field(Id id) throws Exception {
	match('.');
	Field f = null;
	Id rec = top.get(id.op);
	if (rec == null)
	    error(id.toString()+" undeclared");
	if (rec.type.tag != Tag.RECORD)
	    error(rec.toString()+" is not a record");
	Env.push(top);
	top = ((Record)(rec.type)).env;
	Token tok = look;
	match(Tag.ID);
	Id fld = top.get(tok);
	if (fld == null)
	    error(id.toString()+" has no such field: "+tok.toString());
	top = Env.pop();		// restore 'top' for offset()
	if (! Type.numeric(fld.type)) {// array
	    f = new Field(new Token('.'), rec, offset(fld));
	}
	else {
	    f = new Field(new Token('.'), rec, fld);
	}
	return f;
    }
}
